package com.danicsoft.daide.binary;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

import com.danicsoft.daide.historian.BinaryHistorian;

/**
 * Handles all communications for a server
 * 
 * @author Daniel Yule (daniel.yule@gmail.com)
 * @version 1.0
 */
public class ServerConnection extends Connection {

	/**
	 * Create a new ServerConnection from the given criteria.
	 * 
	 * @param socket
	 *        The socket to connect on.
	 * @param historian
	 *        The historian we will use to log problems
	 */
	private ServerConnection(Socket socket, BinaryHistorian historian) {
		super(socket.getInetAddress(), socket.getPort(), historian);
		this.socket = socket;
		setState(CONNECTION_STATE.CONNECTING);
	}
	

	@Override
	public synchronized void connect(ByteMessageHandler byteHandler) throws IOException {
		super.connect(byteHandler);
		connect_to_socket(socket);
		waitForConnected();
		

	}

	@Override
	protected synchronized void handleDiplomaticMessage(byte[] message) {
		if (getState() != CONNECTION_STATE.CONNECTED) {

			sendErrorMessage(IM_NOT_FIRST);

		}

		parseDiplomaticMessage(message);

	}

	@Override
	protected void handleErrorMessage(byte[] message) {
		historian.logConnectionError("Client reports: " + getErrorMessage(message[1]), this);

		severConnection();

	}

	@Override
	protected void handleInitialMessage(byte[] message) {
		int version = ((message[0] & 0xFF) << 8 | message[1] & 0xFF) & 0xFFFF;
		int magicNumber = ((message[2] & 0xFF) << 8 | message[3] & 0xFF) & 0xFFFF;

		if (version != 1) {
			sendErrorMessage(VERSION_INCOMPATIBILITY);
		}
		if (magicNumber != 0xDA10) {
			if (magicNumber == 0x10DA) {
				sendErrorMessage(IM_WRONG_ENDIAN);
			}
			else
				sendErrorMessage(IM_WRONG_NUMBER);

			return;
		}

		if (getState() == CONNECTION_STATE.CONNECTED) {
			sendErrorMessage(MULTIPLE_IM);
			return;
		}
		try {
			send(createRepresentationMessage());
		}
		catch (DisconnectedException e) {
			severConnection();
		}
		setState(CONNECTION_STATE.CONNECTED);

	}

	@Override
	protected void handleRepresentationMessage(byte[] message) {
		sendErrorMessage(CLIENT_RM);
	}

	/**
	 * Listens for connections on the given port. When a new connection is
	 * found, it calls the <code>newConnection()</code> method of the
	 * connection handler.
	 * 
	 * @param port
	 *        The port to listen on.
	 * @param cHandler
	 *        The handler to call when a new connection is found
	 * @param historian
	 *        The historian to use to log status changes.
	 * @throws IOException
	 *         If we could not listen on the port specified.
	 */
	public static void listen(int port, StateChangeHandler cHandler, BinaryHistorian historian) throws IOException {
		ServerSocket sSocket = new ServerSocket(port);
		new Listener(sSocket, cHandler, historian);

	}
	
	/**
	 * Listens for connections on the given port. When a new connection is
	 * found, it calls the <code>newConnection()</code> method of the
	 * connection handler.
	 * 
	 * @param port
	 *        The port to listen on.
	 * @param cHandler
	 *        The handler to call when a new connection is found

	 * @throws IOException
	 *         If we could not listen on the port specified.
	 */
	public static void listen(int port, StateChangeHandler cHandler) throws IOException {
		listen(port, cHandler, new BinaryHistorian());

	}

	/**
	 * Listens for new connections on the port given. When one is found, the
	 * handler is called.
	 * 
	 * @author Daniel Yule (daniel.yule@gmail.com)
	 * @version 1.0
	 */
	private static class Listener extends Thread {

		/**
		 * The server socket we're listening on, already initialized.
		 */
		private ServerSocket sSocket;

		/**
		 * The handler to call when a new connection is created.
		 */
		private StateChangeHandler cHandler;

		/**
		 * The historian to use to log messages with.
		 */
		private BinaryHistorian historian;

		/**
		 * If we're still listening or not.
		 */

		private boolean listening = true;

		/**
		 * Creates a new listener that listens using hte specifed server socket
		 * and notifies the given handler.
		 * 
		 * @param sSocket
		 *        The <code>ServerSocket</code> to listen on
		 * @param cHandler
		 *        The handler we'll call when a new connection is avaliable.
		 * @param historian
		 *        The historian we'll use for logging status changes.
		 */
		public Listener(ServerSocket sSocket, StateChangeHandler cHandler, BinaryHistorian historian) {
			this.sSocket = sSocket;
			this.cHandler = cHandler;
			this.historian = historian;
			start();
		}

		/**
		 * Stops listening on for new connections and closes the connection.
		 */
		public void stopListening() {
			listening = false;
			try {
				sSocket.close();
			}
			catch (IOException e) {

			}
		}

		@Override
		public void run() {
			while (listening) {
				try {
					Socket newSocket = sSocket.accept();
					cHandler.newConnection(new ServerConnection(newSocket, historian));

				}
				catch (IOException e) {
					listening = false;
				}
			}
		}

	}

}
